#import "SourceListViewController.h"
#import "RNSourceList.h"
#import "RNSourceListCellWithCountBadge.h"
#import "RNConstants.h"
#import "NSShadow+MCAdditions.h"
#import "NSManagedObject+SRAdditions.h"
#import "NSManagedObjectContext+SRAdditions.h"
#import "NSTreeController+SRAdditions.h"
#import "RNSourceListHeader.h"
#import "RNSourceListManageHeader.h"
#import "RNSourceListPlaceholder.h"
#import "RNList.h"
#import "RNCategory.h"
#import "RNAisle.h"
#import "RNListItem.h"
#import "RNStore.h"
#import "RNHistoryItem.h"
#import "RNCatalog.h"
#import "RNSearch.h"
#import "RNHistory.h"
#import "RNFavorites.h"
#import "MainWindowController.h"

static NSString* const RNSourceListSelectedRowKey = @"RNSourceListSelectedRowKey";

@interface SourceListViewController (SourceListViewControllerPrivateMethods)
-(void)buildHeaders_;
-(void)updateSelectionFromDefaults_;
@end

#pragma mark -
@implementation SourceListViewController

@synthesize sourceList = sourceList_;
@synthesize sourceListHeaders = sourceListHeaders_;
@synthesize sourceListTreeController = sourceListTreeController_;
@synthesize listsController = listsController_;
@synthesize storesController = storesController_;
@synthesize sortDescriptors = sortDescriptors_;
@synthesize sourceListTreeControllerBindingsEstablished = sourceListTreeControllerBindingsEstablished_;
@synthesize displayingStores = displayingStores_;
@synthesize storesHeader = storesHeader_;
@synthesize ignoreSelectionChange = ignoreSelectionChange_;

- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle windowController:(XSWindowController *)windowController managedObjectContext:(NSManagedObjectContext *)managedObjectContext {
    if (!(self = [super initWithNibName:name bundle:bundle windowController:windowController managedObjectContext:managedObjectContext]))
        return nil;
    
    [self setSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:RNTitleKey ascending:YES selector:@selector(caseInsensitiveCompare:)]]];
//    NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
//    [noteCenter addObserver:self selector:@selector(rootObjectDidChange:) name:RNRootObjectDidChangeNotification object:nil];
    return self;
}

-(void)awakeFromNib {
    NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
    [noteCenter addObserver:self selector:@selector(listItemChangedCompletionState:) name:RNListItemCompletionChangedNotification object:nil];
//    [noteCenter addObserver:self selector:@selector(sourceListSelectionDidChange:) name:NSOutlineViewSelectionDidChangeNotification object:[self sourceList]];
    [noteCenter addObserver:self selector:@selector(managedObjectsDidChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];
    // Temporarily observe tree controller to determine when all data is fully ready so we can restore the last user selection
    [[self sourceListTreeController] addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:NULL];
//    DetailViewController *detailController = [(MainWindowController *)[self windowController] detailViewController];
//    [detailController addObserver:self forKeyPath:@"rootObject" options:NSKeyValueObservingOptionNew context:NULL];
    [self performSelector:@selector(buildHeaders_) withObject:nil afterDelay:0];
    [[self sourceList] registerForDraggedTypes:[NSArray arrayWithObject:RNDragDropUTI]];
    [[self sourceList] setTrackingRow:-1];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {    
    // Observing changes in arranged objects of LISTS and STORES headers
    // Ensure source list remains fully expanded as objects are added/deleted
    [[self sourceList] expandItem:nil expandChildren:YES];
    
    // Temporarily observing source list's tree controller waiting for bindings to be established so we can set the selection
    if ([self sourceListTreeControllerBindingsEstablished] || object != [self sourceListTreeController]) 
        return;
    [self setSourceListTreeControllerBindingsEstablished:YES];
    [[self sourceListTreeController] removeObserver:self forKeyPath:@"arrangedObjects"];
    [self updateSelectionFromDefaults_];
}

-(void)deleteSelectedItem {
    NSArray *selectedNodes = [[self sourceListTreeController] selectedNodes];
    if ([selectedNodes count] != 1) {
        NSBeep();
        return;
    }
    NSTreeNode *selectedNode = [selectedNodes objectAtIndex:0];
    NSTreeNode *parentNode = [selectedNode parentNode];
    id selectedItem = [selectedNode representedObject];
    if (![selectedItem isKindOfClass:[NSManagedObject class]]) {
        NSBeep();
        return;
    }
    
    NSIndexPath *newSelectionIndexPath = nil;
    if ([[parentNode childNodes] count] > 1) {
        newSelectionIndexPath = [[selectedNode indexPath] indexPathByRemovingLastIndex];
        newSelectionIndexPath = [newSelectionIndexPath indexPathByAddingIndex:0];
    }
    
    [[self managedObjectContext] deleteObject:[[selectedNodes objectAtIndex:0] representedObject]];
    [[self sourceListTreeController] performSelector:@selector(setSelectionIndexPath:) withObject:newSelectionIndexPath afterDelay:0];
}


#pragma mark -
#pragma mark Private methods

-(void)buildHeaders_ {
    NSMutableArray *headersArray = [NSMutableArray array];
    
    RNSourceListHeader *listsHeader = [[RNSourceListHeader alloc] initWithTitle:@"LISTS" withArrayController:[self listsController]];
    [listsHeader setNullPlaceholderWithString:@"Placeholder"];
    [headersArray addObject:listsHeader];
    
    [self setStoresHeader:[[RNSourceListHeader alloc] initWithTitle:@"STORES" withArrayController:[self storesController]]];
    [[[self storesHeader] childController] addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionOld context:NULL];
    NSArray *stores = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameStore withPredicate:nil];
    if ([stores count] > 0) {
        [self setDisplayingStores:YES];
        [headersArray addObject:[self storesHeader]];
    }
    
    RNSourceListManageHeader *manageHeader = [[RNSourceListManageHeader alloc] initWithManagedObjectContext:[self managedObjectContext]];
    [headersArray addObject:manageHeader];
    
    [self setSourceListHeaders:headersArray];
    [[self sourceList] expandItem:nil expandChildren:YES];
}


#pragma mark -
#pragma mark Source List Delegate Methods

- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
    id object = [item representedObject];
    return ([object isKindOfClass:[RNSourceListHeader class]] || [object isKindOfClass:[RNSourceListManageHeader class]]);
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item {
    if ([[item representedObject] isKindOfClass:[RNSourceListPlaceholder class]]) 
        return NO;
    return ![self outlineView:outlineView isGroupItem:item];
}

#pragma mark -
#pragma mark Actions

-(IBAction)selectLists:(id)sender {
    RNSourceListHeader *listsHeader = [[self sourceListHeaders] objectAtIndex:0];
    NSTreeNode *listsNode = [[self sourceListTreeController] treeNodeForObject:listsHeader];
    NSArray *childNodes = [listsNode childNodes];
    NSArray *selectedNodes = [[self sourceListTreeController] selectedNodes];
    NSTreeNode *selectedNode = [selectedNodes count] > 0 ? [selectedNodes objectAtIndex:0] : nil;
    if (!selectedNode || ![[selectedNode representedObject] isKindOfClass:[RNList class]]) {
        NSTreeNode *firstChild = [childNodes count] > 0 ? [childNodes objectAtIndex:0] : nil;
        [[self sourceListTreeController] setSelectionIndexPath:[firstChild indexPath]];
        return;
    }
    NSUInteger selectedIndex = [childNodes indexOfObject:selectedNode] + 1;
    if (selectedIndex >= [childNodes count]) 
        selectedIndex = 0;
    NSTreeNode *newNode = [childNodes objectAtIndex:selectedIndex];
    [[self sourceListTreeController] setSelectionIndexPath:[newNode indexPath]];
}

-(IBAction)selectStores:(id)sender {
    RNSourceListHeader *listsHeader = [[self sourceListHeaders] objectAtIndex:1];
    NSTreeNode *storesNode = [[self sourceListTreeController] treeNodeForObject:listsHeader];
    NSArray *childNodes = [storesNode childNodes];
    NSArray *selectedNodes = [[self sourceListTreeController] selectedNodes];
    NSTreeNode *selectedNode = [selectedNodes count] > 0 ? [selectedNodes objectAtIndex:0] : nil;
    if (!selectedNode || ![[selectedNode representedObject] isKindOfClass:[RNStore class]]) {
        NSTreeNode *firstChild = [childNodes count] > 0 ? [childNodes objectAtIndex:0] : nil;
        [[self sourceListTreeController] setSelectionIndexPath:[firstChild indexPath]];
        return;
    }
    NSUInteger selectedIndex = [childNodes indexOfObject:selectedNode] + 1;
    if (selectedIndex >= [childNodes count]) 
        selectedIndex = 0;
    NSTreeNode *newNode = [childNodes objectAtIndex:selectedIndex];
    [[self sourceListTreeController] setSelectionIndexPath:[newNode indexPath]];
    // TODO: CHECK FIRST RESPONDER AFTER THIS SELECTION
}

-(IBAction)selectCatalog:(id)sender {
    NSTreeNode *catalogNode = [[self sourceListTreeController] treeNodeForObject:[RNCatalog sharedCatalog]];
    [[self sourceListTreeController] setSelectionIndexPath:[catalogNode indexPath]];
}

-(IBAction)selectFavorites:(id)sender {
    NSTreeNode *favoritesNode = [[self sourceListTreeController] treeNodeForObject:[RNFavorites sharedFavorites]];
    [[self sourceListTreeController] setSelectionIndexPath:[favoritesNode indexPath]];
}

-(IBAction)selectHistory:(id)sender {
    NSTreeNode *historyNode = [[self sourceListTreeController] treeNodeForObject:[RNHistory sharedHistory]];
    [[self sourceListTreeController] setSelectionIndexPath:[historyNode indexPath]];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
    if ([menuItem action] == @selector(selectLists:)) {
        NSArray *lists = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameList withPredicate:nil];
        return [lists count] > 0;
    }
    if ([menuItem action] == @selector(selectStores:)) {
        NSArray *stores = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameStore withPredicate:nil];
        return [stores count] > 0;
    }
    return YES;
}

-(IBAction)renameSelectedSourceListItem:(id)sender {
    NSInteger selectedRow = [[self sourceList] selectedRow];
    if (selectedRow == -1)
        return;
    [[self sourceList] editColumn:0 row:selectedRow withEvent:nil select:YES];
}

-(IBAction)deleteSelectedSourceListItem:(id)sender {
    [self deleteSelectedItem];
//    NSInteger selectedRow = [[self sourceList] selectedRow];
//    if (selectedRow == -1)
//        return;
//    NSManagedObject *selectedObject = [[[self sourceList] itemAtRow:selectedRow] representedObject];
//    [[self managedObjectContext] deleteObject:selectedObject];
}


#pragma mark -
#pragma mark Notifications

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item {
    return NO;
}

- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
    NSIndexPath *selectionPath = [[self sourceListTreeController] selectionIndexPath];
    if (!selectionPath) {
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:RNSourceListSelectedRowKey];
        return;
    }
    NSData *selectionData = [NSKeyedArchiver archivedDataWithRootObject:selectionPath];
    [[NSUserDefaults standardUserDefaults] setObject:selectionData forKey:RNSourceListSelectedRowKey];
}

-(void)updateSelectionFromDefaults_ {
    NSData *selectionData = [[NSUserDefaults standardUserDefaults] objectForKey:RNSourceListSelectedRowKey];
    if (!selectionData)
        return;
    NSIndexPath *selectionPath = [NSKeyedUnarchiver unarchiveObjectWithData:selectionData];
    NSTreeNode *selectionNode = [[[self sourceListTreeController] arrangedObjects] descendantNodeAtIndexPath:selectionPath];
    if (![self outlineView:[self sourceList] shouldSelectItem:selectionNode])
        selectionPath = nil;
    [[self sourceListTreeController] setSelectionIndexPath:selectionPath];
}

-(void)managedObjectsDidChange:(NSNotification *)note {
    // When stores and lists are created, select them in the source list
    NSSet *inserted = [[note userInfo] valueForKey:NSInsertedObjectsKey];
    inserted = [inserted filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@ OR entity.name == %@", RNEntityNameStore, RNEntityNameList]];
    if ([inserted count] > 0) {
        id insertedObject = [inserted anyObject];
        NSTreeNode *insertedNode = [[self sourceListTreeController] treeNodeForObject:insertedObject];
        NSInteger rowIndex = [[self sourceList] rowForItem:insertedNode];
        [[self sourceList] selectRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex] byExtendingSelection:NO];
    }
    // If last list is deleted, this ensures that the 'Click to add list' link gets its tracking area updated
    NSSet *deleted = [[note userInfo] valueForKey:NSDeletedObjectsKey];
    deleted = [deleted filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", RNEntityNameList]];
    if ([deleted count] > 0) {
        [[self sourceList] performSelector:@selector(updateTrackingAreas) withObject:nil afterDelay:0];
    }    
    NSArray *stores = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameStore withPredicate:nil];
    if ([self displayingStores] && [stores count] == 0) {
        [self setDisplayingStores:NO];
        [self willChangeValueForKey:@"sourceListHeaders"];
        [[self sourceListHeaders] removeObject:[self storesHeader]];
        [self didChangeValueForKey:@"sourceListHeaders"];
        return;
    }
    if (![self displayingStores] && [stores count] > 0) {
        [self setDisplayingStores:YES];
        [self willChangeValueForKey:@"sourceListHeaders"];
        [[self sourceListHeaders] insertObject:[self storesHeader] atIndex:1];
        [self didChangeValueForKey:@"sourceListHeaders"];
        [[self sourceList] expandItem:[[self sourceListTreeController] treeNodeForObject:[self storesHeader]] expandChildren:YES];
        return;
    }
}

-(void)listItemChangedCompletionState:(NSNotification *)note {
    RNListItem *changedItem = [note object];
    RNList *affectedList = [changedItem list];
    NSTreeNode *listNode = [[self sourceListTreeController] treeNodeForObject:affectedList];
    NSInteger listRow = [[self sourceList] rowForItem:listNode];
    [[self sourceList] setNeedsDisplayInRect:[[self sourceList] rectOfRow:listRow]];
}


#pragma mark -
#pragma mark Drag and Drop

-(NSDictionary *)objectArraysFromPasteboard:(NSPasteboard *)pasteboard {
    NSArray *pasteboardItems = [pasteboard pasteboardItems];
    if ([pasteboardItems count] != 1)
        return nil;
    NSPasteboardItem *pasteboardItem = [pasteboardItems objectAtIndex:0];
    NSData *pasteboardData = [pasteboardItem dataForType:RNDragDropUTI];
    return [NSKeyedUnarchiver unarchiveObjectWithData:pasteboardData];
}

-(NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)draggingInfo proposedItem:(id)dropNode proposedChildIndex:(NSInteger)childIndex {
    if (!dropNode)
        return NSDragOperationNone;
    // Don't allow drops between items
    if (childIndex != NSOutlineViewDropOnItemIndex)
        return NSDragOperationNone;
    
    NSDictionary *objectArrays = [self objectArraysFromPasteboard:[draggingInfo draggingPasteboard]];
    if (!objectArrays)
        return NSDragOperationNone;
    
    NSArray *entityNames = [objectArrays allKeys];
    id dropItem = [dropNode representedObject];
    if ([dropItem respondsToSelector:@selector(canContainObjectsWithEntityNames:)] && [dropItem canContainObjectsWithEntityNames:entityNames]) 
        return NSDragOperationCopy;
    
    return NSDragOperationNone;
}

-(BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)draggingInfo item:(id)node childIndex:(NSInteger)childIndex {
    NSAssert(childIndex == NSOutlineViewDropOnItemIndex, @"DetailViewController:acceptDrop: childIndex not NSOutlineViewDropOnItemIndex");
    NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
    NSDictionary *objectArrays = [self objectArraysFromPasteboard:pasteboard];
    if (!objectArrays)
        return NO;
    
    id item = [node representedObject];
    for (NSString *entityName in objectArrays) {
        NSArray *objectArray = [objectArrays valueForKey:entityName];
        for (NSDictionary *dictionaryRepresentation in objectArray) {
            id destinationObject = nil;
            if ([entityName isEqualToString:RNEntityNameItem] && [item isKindOfClass:[RNList class]]) {
                RNItem *sourceItem = (RNItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                NSSet *itemsInList = [[item items] valueForKey:@"item"];
                if ([itemsInList containsObject:sourceItem])
                    continue;
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameListItem inManagedObjectContext:[self managedObjectContext]];
                [destinationObject setItem:sourceItem];
            }
            else if ([entityName isEqualToString:RNEntityNameListItem] && [item isKindOfClass:[RNList class]]) {
                RNListItem *sourceListItem = (RNListItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                RNItem *sourceItem = [sourceListItem item];
                NSSet *itemsInList = [[item items] valueForKey:@"item"];
                if ([itemsInList containsObject:sourceItem])
                    continue;
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[self managedObjectContext]];
            }
            else if ([entityName isEqualToString:RNEntityNameAisle])
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[self managedObjectContext]];
            else if (([entityName isEqualToString:RNEntityNameListItem] || [entityName isEqualToString:RNEntityNameItem]) && [item isKindOfClass:[RNStore class]]) {
                [NSApp sendAction:@selector(changeAisleForSelectedItems:) to:nil from:self];
                return YES;
            }
            else if ([entityName isEqualToString:RNEntityNameHistoryItem] && [item isKindOfClass:[RNList class]]) {
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameListItem inManagedObjectContext:[self managedObjectContext]];
                RNHistoryItem *sourceHistoryItem = (RNHistoryItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                RNItem *sourceItem = [sourceHistoryItem item];
                [destinationObject setCategory:[sourceItem category]];
                [destinationObject setAisles:[sourceItem aisles]];
            }
            else {
                NSURL *objectURI = [dictionaryRepresentation valueForKey:RNDragDropObjectURIKey];
                destinationObject = [[self managedObjectContext] objectWithURI:objectURI];
                [destinationObject moveToContainer:item];
                continue;
            }
            [destinationObject initializeWithDictionary:dictionaryRepresentation];
            [destinationObject moveToContainer:item];
        }
    }
    return YES;
}

@end
